/*
* Conditions Of Use 
* 
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
* 
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
* 
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*  
* .
* 
*/
/*******************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD).        *
 *******************************************************************************/
package gov.nist.javax.sip.address;

import gov.nist.core.*;

import java.lang.reflect.*;

/**
 * Root object for all objects in this package.
 *
 * @version 1.2 $Revision: 1.9 $ $Date: 2007/10/22 03:38:23 $
 *
 * @author M. Ranganathan   <br/>
 *
 * 
 */
public abstract class NetObject extends GenericObject {

	protected static final String CORE_PACKAGE = PackageNames.CORE_PACKAGE;
	protected static final String NET_PACKAGE = PackageNames.NET_PACKAGE;
	protected static final String PARSER_PACKAGE = PackageNames.PARSER_PACKAGE;
	protected static final String UDP = "udp";
	protected static final String TCP = "tcp";
	protected static final String TRANSPORT = "transport";
	protected static final String METHOD = "method";
	protected static final String USER = "user";
	protected static final String PHONE = "phone";
	protected static final String MADDR = "maddr";
	protected static final String TTL = "ttl";
	protected static final String LR = "lr";
	protected static final String SIP = "sip";
	protected static final String SIPS = "sips";

	// Added by Daniel J. Martinez Manzano <dani@dif.um.es>
	protected static final String TLS = "tls";
	
	// Added by Peter Musgrave <pmusgrave@newheights.com>
	// params for outbound and gruu drafts
	protected static final String GRUU = "gr";


	/** Default constructor
	 */
	public NetObject() {
		super();
	}

	/**
	 * An introspection based equality predicate for SIPObjects.
	 *@param that is the other object to test against.
	 */
	public boolean equals(Object that) {
		if (!this.getClass().equals(that.getClass()))
			return false;
		Class<?> myclass = this.getClass();
		Class<?> hisclass = that.getClass();
		while (true) {
			Field[] fields = myclass.getDeclaredFields();
			Field[] hisfields = hisclass.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Field f = fields[i];
				Field g = hisfields[i];
				// Only print protected and public members.
				int modifier = f.getModifiers();
				if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE)
					continue;
				Class<?> fieldType = f.getType();
				String fieldName = f.getName();
				if (fieldName.compareTo("stringRepresentation") == 0) {
					continue;
				}
				if (fieldName.compareTo("indentation") == 0) {
					continue;
				}
				try {
					// Primitive fields are printed with type: value
					if (fieldType.isPrimitive()) {
						String fname = fieldType.toString();
						if (fname.compareTo("int") == 0) {
							if (f.getInt(this) != g.getInt(that))
								return false;
						} else if (fname.compareTo("short") == 0) {
							if (f.getShort(this) != g.getShort(that))
								return false;
						} else if (fname.compareTo("char") == 0) {
							if (f.getChar(this) != g.getChar(that))
								return false;
						} else if (fname.compareTo("long") == 0) {
							if (f.getLong(this) != g.getLong(that))
								return false;
						} else if (fname.compareTo("boolean") == 0) {
							if (f.getBoolean(this) != g.getBoolean(that))
								return false;
						} else if (fname.compareTo("double") == 0) {
							if (f.getDouble(this) != g.getDouble(that))
								return false;
						} else if (fname.compareTo("float") == 0) {
							if (f.getFloat(this) != g.getFloat(that))
								return false;
						}
					} else if (g.get(that) == f.get(this))
						continue;
					else if (f.get(this) == null && g.get(that) != null)
						return false;
					else if (g.get(that) == null && f.get(that) != null)
						return false;
					else if (!f.get(this).equals(g.get(that)))
						return false;
				} catch (IllegalAccessException ex1) {
					InternalErrorHandler.handleException(ex1);
				}
			}
			if (myclass.equals(NetObject.class))
				break;
			else {
				myclass = myclass.getSuperclass();
				hisclass = hisclass.getSuperclass();
			}
		}
		return true;
	}

	
	

	/** An introspection based predicate matching using a template
	 * object. Allows for partial match of two protocl Objects.
	 *@param other the match pattern to test against. The match object
	 * has to be of the same type (class). Primitive types
	 * and non-sip fields that are non null are matched for equality.
	 * Null in any field  matches anything. Some book-keeping fields
	 * are ignored when making the comparison.
	 *@return true if match succeeds false otherwise.
	 */

	public boolean match(Object other) {
		if (other == null)
			return true;
		if (!this.getClass().equals(other.getClass()))
			return false;
		GenericObject that = (GenericObject) other;
		// System.out.println("Comparing " + that.encode());
		// System.out.println("this = " + this.encode());

		Class<?> hisclass = other.getClass();
		Class<?> myclass = this.getClass();
		while (true) {
			Field[] fields = myclass.getDeclaredFields();
			Field[] hisfields = hisclass.getDeclaredFields();
			for (int i = 0; i < fields.length; i++) {
				Field f = fields[i];
				Field g = hisfields[i];
				// Only print protected and public members.
				int modifier = f.getModifiers();
				if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE)
					continue;
				Class<?> fieldType = f.getType();
				String fieldName = f.getName();
				if (fieldName.compareTo("stringRepresentation") == 0) {
					continue;
				}
				if (fieldName.compareTo("indentation") == 0) {
					continue;
				}
				try {
					// Primitive fields are printed with type: value
					if (fieldType.isPrimitive()) {
						String fname = fieldType.toString();
						if (fname.compareTo("int") == 0) {
							if (f.getInt(this) != g.getInt(that))
								return false;
						} else if (fname.compareTo("short") == 0) {
							if (f.getShort(this) != g.getShort(that))
								return false;
						} else if (fname.compareTo("char") == 0) {
							if (f.getChar(this) != g.getChar(that))
								return false;
						} else if (fname.compareTo("long") == 0) {
							if (f.getLong(this) != g.getLong(that))
								return false;
						} else if (fname.compareTo("boolean") == 0) {
							if (f.getBoolean(this) != g.getBoolean(that))
								return false;
						} else if (fname.compareTo("double") == 0) {
							if (f.getDouble(this) != g.getDouble(that))
								return false;
						} else if (fname.compareTo("float") == 0) {
							if (f.getFloat(this) != g.getFloat(that))
								return false;
						}
					} else {
						Object myObj = f.get(this);
						Object hisObj = g.get(that);
						if (hisObj != null && myObj == null)
							return false;
						else if (hisObj == null && myObj != null)
							continue;
						else if (hisObj == null && myObj == null)
							continue;
						else if (
							hisObj instanceof java.lang.String
								&& myObj instanceof java.lang.String) {
							if (((String) hisObj).equals(""))
								continue;
							if (((String) myObj)
								.compareToIgnoreCase((String) hisObj)
								!= 0)
								return false;
						} else if (
							GenericObject.isMySubclass(myObj.getClass())
								&& GenericObject.isMySubclass(hisObj.getClass())
								&& myObj.getClass().equals(hisObj.getClass())
								&& ((GenericObject) hisObj).getMatcher()
									!= null) {
							String myObjEncoded =
								((GenericObject) myObj).encode();
							boolean retval =
								((GenericObject) hisObj).getMatcher().match(
									myObjEncoded);
							if (!retval)
								return false;
						} else if (
							GenericObject.isMySubclass(myObj.getClass())
								&& !((GenericObject) myObj).match(hisObj))
							return false;
						else if (
							GenericObjectList.isMySubclass(myObj.getClass())
								&& !((GenericObjectList) myObj).match(hisObj))
							return false;
					}
				} catch (IllegalAccessException ex1) {
					InternalErrorHandler.handleException(ex1);
				}
			}
			if (myclass.equals(NetObject.class))
				break;
			else {
				myclass = myclass.getSuperclass();
				hisclass = hisclass.getSuperclass();
			}
		}
		return true;
	}

	/**
	 * An introspection based string formatting method. We need this because
	 * in this package (although it is an exact duplicate of the one in
	 * the superclass) because it needs to access the protected members
	 * of the other objects in this class.
	 * @return String
	 */
	public String debugDump() {
		stringRepresentation = "";
		Class<?> myclass = getClass();
		sprint(myclass.getName());
		sprint("{");
		Field[] fields = myclass.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			// Only print protected and public members.
			int modifier = f.getModifiers();
			if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE)
				continue;
			Class<?> fieldType = f.getType();
			String fieldName = f.getName();
			if (fieldName.compareTo("stringRepresentation") == 0) {
				// avoid nasty recursions...
				continue;
			}
			if (fieldName.compareTo("indentation") == 0) {
				// formatting stuff - not relevant here.
				continue;
			}
			sprint(fieldName + ":");
			try {
				// Primitive fields are printed with type: value
				if (fieldType.isPrimitive()) {
					String fname = fieldType.toString();
					sprint(fname + ":");
					if (fname.compareTo("int") == 0) {
						int intfield = f.getInt(this);
						sprint(intfield);
					} else if (fname.compareTo("short") == 0) {
						short shortField = f.getShort(this);
						sprint(shortField);
					} else if (fname.compareTo("char") == 0) {
						char charField = f.getChar(this);
						sprint(charField);
					} else if (fname.compareTo("long") == 0) {
						long longField = f.getLong(this);
						sprint(longField);
					} else if (fname.compareTo("boolean") == 0) {
						boolean booleanField = f.getBoolean(this);
						sprint(booleanField);
					} else if (fname.compareTo("double") == 0) {
						double doubleField = f.getDouble(this);
						sprint(doubleField);
					} else if (fname.compareTo("float") == 0) {
						float floatField = f.getFloat(this);
						sprint(floatField);
					}
				} else if (GenericObject.class.isAssignableFrom(fieldType)) {
					if (f.get(this) != null) {
						sprint(
							((GenericObject) f.get(this)).debugDump(
								indentation + 1));
					} else {
						sprint("<null>");
					}

				} else if (
					GenericObjectList.class.isAssignableFrom(fieldType)) {
					if (f.get(this) != null) {
						sprint(
							((GenericObjectList) f.get(this)).debugDump(
								indentation + 1));
					} else {
						sprint("<null>");
					}

				} else {
					// Dont do recursion on things that are not
					// of our header type...
					if (f.get(this) != null) {
						sprint(f.get(this).getClass().getName() + ":");
					} else {
						sprint(fieldType.getName() + ":");
					}

					sprint("{");
					if (f.get(this) != null) {
						sprint(f.get(this).toString());
					} else {
						sprint("<null>");
					}
					sprint("}");
				}
			} catch (IllegalAccessException ex1) {
				continue; // we are accessing a private field...
			}
		}
		sprint("}");
		return stringRepresentation;
	}

	
	

	/**
	 * Formatter with a given starting indentation (for nested structs).
	 * @param indent int to set
	 * @return String
	 */
	public String debugDump(int indent) {
		int save = indentation;
		indentation = indent;
		String retval = this.debugDump();
		indentation = save;
		return retval;
	}

	/** Encode this to a string.
	 *
	 *@return string representation for this object.
	 */
	public String toString() {
		return this.encode();
	}
}
